#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <GL/glui.h>

#include <stack>
#include <iostream>
#include "../pixelMap/pixelMap.h"


///Ações GLUI

#define LOAD_IMAGE      0x01
#define SEGMENTATION    0x02
#define BG_COLOR        0x03

#define SEG_COLOR_INI   255
#define BG_COLOR_INI    0
#define INPUT           ".bmp"

using namespace std;

//GLUT
void idleFunc(void);
void imageDisplay(void);
void imageInit(void);
void mouse(int button, int state, int x, int y);
void segImageDisplay(void);
void segImageInit(void);
void applySegmentation(int h, int w, int inf, int sup);

int winImage, winSegImage;

///GLUI
void controlCallback(int control);

GLUI* glui;
GLUI_Panel* panelMain;

GLUI_Panel* panelImage;
GLUI_EditText *editImage;
GLUI_Button* buttonImage;

GLUI_Panel* panelThreshold;
GLUI_Spinner* spinnerThresholdMin;
GLUI_Spinner* spinnerThresholdMax;

GLUI_Panel* panelSegColor;
GLUI_Spinner* spinnerSegColor;
GLUI_Spinner* spinnerBGColor;

GLUI_Checkbox* checkboxMarked;

///Outras Declarações

typedef struct
{
    int i, j;
} Pos;

void initMatrices();
void freeMatrices();
void setImage();
void setSegMatrixColor(uByte color);
void resetMark();

PixelMap* imagePM = new PixelMap();
PixelMap* segImagePM = new PixelMap(true);
pixel**   imagePixels = NULL;
pixel**   segImagePixels = NULL;
int**     mark = NULL;
uByte     segColor = SEG_COLOR_INI;
int       imagew, imageh;

int main(int argc, char** argv)
{
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(100, 100);
    glutInitWindowPosition(5, 100);
    winImage = glutCreateWindow("Image");
    glutMouseFunc(mouse);
    glutDisplayFunc(imageDisplay);
    imageInit();

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(100, 100);
    glutInitWindowPosition(125, 100);
    winSegImage = glutCreateWindow("Segmented Image");
    glutDisplayFunc(segImageDisplay);
    segImageInit();

    glui = GLUI_Master.create_glui("Control", 0, 1080, 100);
    panelMain = glui->add_panel("", GLUI_PANEL_NONE);

    panelImage = glui->add_panel_to_panel(panelMain, "Image");
    editImage = glui->add_edittext_to_panel(panelImage, "Image: ");
    buttonImage = glui->add_button_to_panel(panelImage, "Set", LOAD_IMAGE, controlCallback);
    editImage->set_text(INPUT);

    panelThreshold = glui->add_panel_to_panel(panelMain, "Threshold");
    spinnerThresholdMin = glui->add_spinner_to_panel(panelThreshold, "Min: ", GLUI_SPINNER_INT);
    spinnerThresholdMin->set_speed(0.1);
    spinnerThresholdMin->set_int_val(30);
    spinnerThresholdMax = glui->add_spinner_to_panel(panelThreshold, "Max: ", GLUI_SPINNER_INT);
    spinnerThresholdMax->set_speed(0.1);
    spinnerThresholdMax->set_int_val(30);

    panelSegColor = glui->add_panel_to_panel(panelMain, "Colors");
    spinnerSegColor = glui->add_spinner_to_panel(panelSegColor, "Segment Color", GLUI_SPINNER_INT, &segColor);
    spinnerSegColor->set_int_limits(0, 255, GLUI_LIMIT_WRAP);
    spinnerSegColor->set_speed(0.1);
    spinnerSegColor->set_int_val(SEG_COLOR_INI);
    spinnerBGColor = glui->add_spinner_to_panel(panelSegColor, "Background Color", GLUI_SPINNER_INT, NULL, BG_COLOR, controlCallback);
    spinnerBGColor->set_int_limits(0, 255, GLUI_LIMIT_WRAP);
    spinnerBGColor->set_speed(0.1);
    spinnerBGColor->set_int_val(BG_COLOR_INI);

    checkboxMarked = glui->add_checkbox_to_panel(panelMain, "Clean marks");
    checkboxMarked->set_int_val(1);

    glui->set_main_gfx_window(winImage);
    GLUI_Master.set_glutIdleFunc(idleFunc);

    glutMainLoop();
    return 0;
}

void idleFunc(void)
{
    if (glutGetWindow() != winImage) glutSetWindow(winImage);
    glutPostRedisplay();
}

void imageDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    imagePM->ViewImage();
    glutSwapBuffers();
    glutPostRedisplay();
}

void segImageDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    segImagePM->ViewImage();
    glutSwapBuffers();
    glutPostRedisplay();
}

void segImageInit(void)
{
    // selecionar cor de fundo
    float bgColor = 255.0 - segColor;
    glClearColor(bgColor, bgColor, bgColor, 0.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
    glLoadIdentity(); // Inicializa com matriz identidade
}

void imageInit(void)
{
    // selecionar cor de fundo (preto)
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
    glLoadIdentity(); // Inicializa com matriz identidade
}

void mouse(int button, int state, int x, int y)
{
    if (state == GLUT_UP) return;
    if (button == GLUT_LEFT_BUTTON)
    {
        cout << "Applyng the segmentation..." << endl;
        int infColor, supColor;
        int i = imageh - y;
        int j = x;
        infColor = (int) imagePixels[i][j].value - spinnerThresholdMin->get_int_val();
        if (infColor < 0) infColor = 0;
        supColor = (int) imagePixels[i][j].value + spinnerThresholdMax->get_int_val();
        if (supColor > 255) supColor = 255;

        applySegmentation(i, j, infColor, supColor);
        segImagePM->ConvertPixelsToData(segImagePixels);
        if( checkboxMarked->get_int_val() ) resetMark();
        glutPostRedisplay();
        cout << "Segmentation Done!!!" << endl;
    }
}

void controlCallback(int control)
{
    switch (control)
    {
        case LOAD_IMAGE:
        {
            if (!imagePixels && !segImagePixels && !mark) freeMatrices();
            setImage();
            break;
        }
        case BG_COLOR:
        {
            setSegMatrixColor((uByte) spinnerBGColor->get_int_val());
            segImagePM->ConvertPixelsToData(segImagePixels);
            resetMark();
            glutPostRedisplay();
            break;
        }
        default: break;
    }
}

void initMatrices()
{
    imagePixels = (pixel **) malloc(imageh * sizeof (pixel *));
    for (int i = 0; i < imageh; i++)
    {
        imagePixels[i] = (pixel *) malloc(imagew * sizeof (pixel));
    }
    imagePM->ConvertDataToPixels(imagePixels);

    segImagePixels = (pixel **) malloc(imageh * sizeof (pixel *));
    for (int i = 0; i < imageh; i++)
    {
        segImagePixels[i] = (pixel *) malloc(imagew * sizeof (pixel));
    }
    setSegMatrixColor((uByte) spinnerBGColor->get_int_val());
    segImagePM->ConvertPixelsToData(segImagePixels);

    mark = (int **) malloc(imageh * sizeof (int *));
    for (int i = 0; i < imageh; i++)
    {
        mark[i] = (int *) malloc(imagew * sizeof (int));
    }
    resetMark();
}

void freeMatrices()
{
    for (int i = 0; i < imageh; i++)
    {
        free(imagePixels[i]);
        free(segImagePixels[i]);
        free(mark[i]);
    }
    free(imagePixels);
    free(segImagePixels);
    free(mark);
}

void setImage()
{
    imagePM->Read(editImage->get_text());
    imagePM->ConvertToGrayScale();

    imageh = imagePM->GetHeight();
    imagew = imagePM->GetWidth();
    segImagePM->SetDimensions(imagew, imageh);

    initMatrices();

    glutSetWindow(winImage);
    glutReshapeWindow(imagew, imageh);
    glutPositionWindow(5, 100);
    glutSetWindow(winSegImage);
    glutReshapeWindow(imagew, imageh);
    glutPositionWindow(imagew + 25, 100);
}

void resetMark()
{
    for (int i = 0; i < imageh; i++)
    {
        for (int j = 0; j < imagew; j++)
        {
            mark[i][j] = 0;
        }
    }
}

void setSegMatrixColor(uByte color)
{
    for (int i = 0; i < imageh; i++)
    {
        for (int j = 0; j < imagew; j++)
        {
            segImagePixels[i][j].value = color;
        }
    }
}

void applySegmentation(int h, int w, int inf, int sup)
{
    stack<Pos*> segStack;
    segStack.push(new Pos);
    ((Pos*) segStack.top())->i = h;
    ((Pos*) segStack.top())->j = w;

    while (!segStack.empty())
    {
//        cout << "Size: " << segStack.size() << endl;
        Pos* position = segStack.top();
        segStack.pop();
        segImagePixels[position->i][position->j].value = segColor;

        //Pushing the neighbors
        for (int i = -1; i < 2; i++)
        {
            for (int j = -1; j < 2; j++)
            {
                int y = position->i + i, x = position->j + j;
                bool a = (y >= 0);
                bool b = (y < imageh);
                bool c = (x >= 0);
                bool d = (x < imagew);
                if (a && b && c && d)
                {
                    bool e = (!mark[y][x]);
                    bool f = (imagePixels[y][x].value >= inf);
                    bool g = (imagePixels[y][x].value <= sup);
                    if (e && f && g)
                    {
                        segStack.push(new Pos);
                        ((Pos*) segStack.top())->i = y;
                        ((Pos*) segStack.top())->j = x;
                        mark[position->i][position->j] = 1;
                    }
                }
            }
        }
        //delete the actual Pos
        delete position;
    }
}
